fibers(1) -- Fiber support for v8 and Node
INSTALLING
via npm
npm install fibers
- You're done! (see "supported platforms" below if you run into errors)
from source
git clone git://github.com/laverdet/node-fibers.git
cd node-fibers
npm install
Note: node-fibers uses node-gyp for
building. To manually invoke the build process, you can use node-gyp rebuild
.
This will put the compiled extension in build/Release/fibers.node
. However,
when you do require('fibers')
, it will expect the module to be in, for
example, bin/linux-x64-v8-3.11/fibers.node
. You can manually put the module
here every time you build, or you can use the included build script. Either
npm install
or node build -f
will do this for you. If you are going to be
hacking on node-fibers, it may be worthwhile to first do node-gyp configure
and then for subsequent rebuilds you can just do node-gyp build
which will
be faster than a full npm install
or node-gyp rebuild
.
meteor users please read this
If you're trying to get meteor running and you ended up at this page you're
probably doing something wrong. Please uninstall all versions of NodeJS and
Meteor, then start over. See
meteor#5124 for more
information.
supported platforms
If you are running NodeJS version 4.x, 5.x, or 6.x on Linux, OS X, or Windows
(7 or later) then you should be able to install fibers from npm just fine. If
you are running an older (or newer) version of node or some other operating
system you will have to compile fibers on your system.
(special thanks to Jeroen Janssen for his work on
fibers in Windows)
If you do end up needing to compile fibers first make sure you have node-gyp
installed as a global dependency (npm install -g node-gyp
), and that you have
setup your build enviroment by following the instructions at
node-gyp. Ubuntu-flavored Linux users
may need to run sudo apt-get install g++
as well.
EXAMPLES
The examples below describe basic use of Fiber
, but note that it is not
recommended to use Fiber
without an abstraction in between your code and
fibers. See "FUTURES" below for additional information.
Sleep
This is a quick example of how you can write sleep() with fibers. Note that
while the sleep() call is blocking inside the fiber, node is able to handle
other events.
$ cat sleep.js
var Fiber = require('fibers');
function sleep(ms) {
var fiber = Fiber.current;
setTimeout(function() {
fiber.run();
}, ms);
Fiber.yield();
}
Fiber(function() {
console.log('wait... ' + new Date);
sleep(1000);
console.log('ok... ' + new Date);
}).run();
console.log('back in main');
$ node sleep.js
wait... Fri Jan 21 2011 22:42:04 GMT+0900 (JST)
back in main
ok... Fri Jan 21 2011 22:42:05 GMT+0900 (JST)
Incremental Generator
Yielding execution will resume back in the fiber right where you left off. You
can also pass values back and forth through yield() and run(). Again, the node
event loop is never blocked while this script is running.
$ cat generator.js
var Fiber = require('fibers');
var inc = Fiber(function(start) {
var total = start;
while (true) {
total += Fiber.yield(total);
}
});
for (var ii = inc.run(1); ii <= 10; ii = inc.run(1)) {
console.log(ii);
}
$ node generator.js
1
2
3
4
5
6
7
8
9
10
Fibonacci Generator
Expanding on the incremental generator above, we can create a generator which
returns a new Fibonacci number with each invocation. You can compare this with
the ECMAScript Harmony
Generator Fibonacci
example.
$ cat fibonacci.js
var Fiber = require('fibers');
function Fibonacci() {
var fiber = Fiber(function() {
Fiber.yield(0);
var prev = 0, curr = 1;
while (true) {
Fiber.yield(curr);
var tmp = prev + curr;
prev = curr;
curr = tmp;
}
});
return fiber.run.bind(fiber);
}
var seq = Fibonacci();
for (var ii = seq(); ii <= 1597; ii = seq()) {
console.log(ii);
}
$ node fibonacci.js
0
1
1
2
3
5
8
13
21
34
55
89
144
233
377
610
987
1597
Basic Exceptions
Fibers are exception-safe; exceptions will continue travelling through fiber
boundaries:
$ cat error.js
var Fiber = require('fibers');
var fn = Fiber(function() {
console.log('async work here...');
Fiber.yield();
console.log('still working...');
Fiber.yield();
console.log('just a little bit more...');
Fiber.yield();
throw new Error('oh crap!');
});
try {
while (true) {
fn.run();
}
} catch(e) {
console.log('safely caught that error!');
console.log(e.stack);
}
console.log('done!');
$ node error.js
async work here...
still working...
just a little bit more...
safely caught that error!
Error: oh crap!
at error.js:11:9
done!
FUTURES
Using the Fiber
class without an abstraction in between your code and the raw
API is not recommended. Fiber
is meant to implement the smallest amount of
functionality in order make possible many different programming patterns. This
makes the Fiber
class relatively lousy to work with directly, but extremely
powerful when coupled with a decent abstraction. There is no right answer for
which abstraction is right for you and your project. Included with node-fibers
is an implementation of "futures" which is fiber-aware. Usage of this library
is documented below. There are several other externally-maintained options
which can be found on the wiki.
You should feel encouraged to be creative with fibers and build a solution
which works well with your project. For instance, Future
is not a good
abstraction to use if you want to build a generator function (see Fibonacci
example above).
Using Future
to wrap existing node functions. At no point is the node event
loop blocked:
$ cat ls.js
var Future = require('fibers/future');
var fs = Future.wrap(require('fs'));
Future.task(function() {
var fileNames = fs.readdirFuture('.').wait();
console.log('Found '+ fileNames.length+ ' files');
var stats = [];
for (var ii = 0; ii < fileNames.length; ++ii) {
stats.push(fs.statFuture(fileNames[ii]));
}
stats.map(function(f) {
f.wait()
});
for (var ii = 0; ii < fileNames.length; ++ii) {
console.log(fileNames[ii]+ ': '+ stats[ii].get().size);
}
}).detach();
$ node ls.js
Found 11 files
bin: 4096
fibers.js: 1708
.gitignore: 37
README.md: 8664
future.js: 5833
.git: 4096
LICENSE: 1054
src: 4096
ls.js: 860
Makefile: 436
package.json: 684
The future API is designed to make it easy to move between classic
callback-style code and fiber-aware waiting code:
$ cat sleep.js
var Future = require('fibers/future'), wait = Future.wait;
function sleep(ms) {
var future = new Future;
setTimeout(function() {
future.return();
}, ms);
return future;
}
var calcTimerDelta = function(ms) {
var start = new Date;
sleep(ms).wait();
return new Date - start;
}.future();
calcTimerDelta(2000).resolve(function(err, val) {
console.log('Set timer for 2000ms, waited '+ val+ 'ms');
});
$ node sleep.js
Set timer for 2000ms, waited 2009ms
API DOCUMENTATION
Fiber's definition looks something like this:
function Fiber(fn) {
[native code]
}
Fiber.current = undefined;
Fiber.yield = function(param) {
[native code]
}
Fiber.prototype.run = function(param) {
[native code]
}
Fiber.prototype.reset = function() {
[native code]
}
Fiber.prototype.throwInto = function(exception) {
[native code]
}
Future's definition looks something like this:
Function.prototype.future = function() { ... }
function Future() {}
Future.wrap = function(fn, multi, suffix) { ... }
Future.task = function(fn) { ... }
Future.wait = function() { ... }
Future.prototype.get = function() { ... }
Future.prototype.return = function(value) { ... }
Future.prototype.throw = function(error) { ... }
Future.prototype.detach = function() { ... }
Future.prototype.isResolved = function() { ... }
Future.prototype.resolver = function() { ... }
Future.prototype.resolve = function() { ... }
Future.prototype.proxy = function(future) { ... }
Future.prototype.wait = function() { ... }
Future.fromPromise = function(promise) { ... }
Future.prototype.promise = function() { ... }
GARBAGE COLLECTION
If you intend to build generators, iterators, or "lazy lists", you should be
aware that all fibers must eventually unwind. This is implemented by causing
yield() to throw unconditionally when the library is trying to unwind your
fiber-- either because reset() was called, or all handles to the fiber were lost
and v8 wants to delete it.
Something like this will, at some point, cause an infinite loop in your
application:
var fiber = Fiber(function() {
while (true) {
try {
Fiber.yield();
} catch(e) {}
}
});
fiber.run();
If you either call reset() on this fiber, or the v8 garbage collector decides it
is no longer in use, the fiber library will attempt to unwind the fiber by
causing all calls to yield() to throw. However, if you catch these exceptions
and continue anyway, an infinite loop will occur.
There are other garbage collection issues that occur with misuse of fiber
handles. If you grab a handle to a fiber from within itself, you should make
sure that the fiber eventually unwinds. This application will leak memory:
var fiber = Fiber(function() {
var that = Fiber.current;
Fiber.yield();
}
fiber.run();
fiber = undefined;
There is no way to get back into the fiber that was started, however it's
impossible for v8's garbage collector to detect this. With a handle to the fiber
still outstanding, v8 will never garbage collect it and the stack will remain in
memory until the application exits.
Thus, you should take care when grabbing references to Fiber.current
.